<!--
 * @Author: cjx
 * @Date: 2023-05-08 14:37:56
 * @LastEditTime: 2023-05-10 17:27:38
 * @Description: 级联组件
 * @props:
   调用样例：<CdCascader ref="cascaderRef" :dictType="this.dictType" :parentValue="this.parentValue" size="default" :parentId="this.parentId" @on-change="onChange"></CdCascader>
    size：非必填，设置下拉框的大小： small large default
    dictData: 数据集 Array
    dictType: 字典类型 string
    parentValue: 字典父级值 string
    parentId: 字典父级id string
    showableRootVals: 配置第一项可选项，若不配置则所有项目可选 string[]
    showableSubVals: 配置第二项可选项，若不配置则所有项目可选 string[]
    showableRootValsReverse: 一级是否反向配置 boolean
    showableSubValsReverse: 二级是否反向配置 boolean
    splitChar: 级联文本拼接符号 string
    needEmptyItem: 是否添加自定义项 boolean
    emptyItemLabel: 自定义项文本 string
    emptyItemValue: 是否添加值 string
    selLastLev: 是否选择到最后一级 Boolean
    selMinLev: 指定选择到的最小选层级 number；与selLastLev不能同时传入
    showLev: 指定级联面板需要展示的层级 Number
    @on-change：选项发生改变时的回调事件

    重置： 实例.value.resetValue(); 
    例：
    const cascaderRef = ref()
    cascaderRef.value.resetValue
-->
<template>
  <Cascader v-if="initFlag" />
  <Cascader
    v-else
    ref="cascaderRef"
    v-model="currtentValue"
    transfer
    :data="dictData"
    :size="props.size"
    :load-data="loadDataHandle"
    :change-on-select="changeOnSelect"
    :disabled="props.disabled"
    :filterable="props.filterable"
    :clearable="props.clearable"
    @on-change="onChangeHandle"
    @on-visible-change="onVisibleChangeHandle"
  />
</template>

<script setup lang="ts">
import { apiFindDictList, apiFindDictItem } from '@/api/common'
import { onBeforeMount, reactive, ref, watch, type Ref } from 'vue'
defineOptions({ name: 'CdCascader' })
const props = defineProps({
  size: String, // 输入框大小
  disabled: Boolean, // 是否禁用
  filterable: Boolean, // 是否支持搜索
  clearable: {
    // 是否支持清除
    type: Boolean,
    default: true
  },

  modelValue: String,
  dictType: {
    // 字典类型
    type: String,
    default: ''
  },
  parentValue: String, // 父级值
  parentId: {
    type: String,
    default: '-1'
  }, // 父级id
  showableRootVals: {
    // 配置第一项可选项，若不配置则所有项目可选
    type: Array,
    default: () => []
  },
  showableSubVals: {
    // 配置第二项可选项，若不配置则所有项目可选
    type: Array,
    default: () => []
  },
  showableRootValsReverse: Boolean, // 一级是否反向配置
  showableSubValsReverse: Boolean, // 二级是否反向配置
  splitChar: {
    // 级联文本拼接符号
    type: String,
    default: '/'
  },
  needEmptyItem: {
    // 是否添加自定义项
    type: Boolean,
    default: false
  },
  // 自定义项文本
  emptyItemLabel: {
    type: String,
    default: '为空'
  },
  // 自定义项值
  emptyItemValue: {
    type: String,
    default: '0'
  },
  selLastLev: {
    // 是否选到最后一级
    type: Boolean,
    default: false
  },
  selMinLev: {
    // 最小选择层级数, 必须小于showLev的值
    type: Number,
    default: 0
  },
  // 级联选择指定展示的层级,默认无限大
  showLev: {
    type: Number,
    validator(val) {
      return val ? +val > 1 : true
    },
    default: 999
  }
})

const emits = defineEmits(['update:modelValue', 'get-full-label', 'get-last-label', 'on-change'])

interface IdictItem {
  children?: IdictItem
  childrenSum?: number
  description?: string
  id: string
  label: string
  lev: number
  parentId: string
  parentValue: string
  remarks: string
  value: string
  loading?: boolean
  disabled?: boolean
}

// 数据初始化
const init = () => {
  initValue()

  getFirstLevDict()
}

// 获取第一级字典
let dictData: any[] = reactive([]) // 级联数据
let initFlag = ref(false) // 数据初始化标识
const getFirstLevDict = () => {
  initFlag.value = true
  apiFindDictList({ type: props.dictType, parentId: props.parentId, parentValue: props.parentValue }).then((res) => {
    const { isSuccess, errorMsg, data } = res || {}
    if (isSuccess) {
      dictData = data.map((item: IdictItem) => {
        item.lev = 1

        // 一级禁用项判断
        if (props.showableRootVals.length) {
          const itemShowJudge = props.showableRootVals.includes(item.value)
          const isItemShow = props.showableRootValsReverse ? itemShowJudge : !itemShowJudge
          // showableSetReverse为true时，符合条件的选项隐藏；为false时，符合条件的展示
          item.disabled = isItemShow
        } else {
          item.disabled = false
        }
        return (item.childrenSum as number) > 0 ? { ...item, children: [], loading: false } : item
      })

      // 添加一级自定义项
      if (props.needEmptyItem) {
        dictData.splice(0, 0, { label: props.emptyItemLabel, value: props.emptyItemValue })
      }
    } else {
      window.$Message.error(errorMsg)
    }
    initFlag.value = false
  })
}

// 回显处理
const cascaderRef = ref() // 组件实例
let currtentValue: Ref<any> = ref([]) // 当前级联选项值
const initValue = async () => {
  if (props.modelValue) {
    currtentValue.value = []
    let curLastLevVal = props.modelValue || '' // 当前最后一层的值
    let findTimes = 0 // 向上追溯次数
    const selectedItems: Array<IdictItem> = [] // 已选项

    // 向上追溯最多5级
    while (findTimes <= 5) {
      const res = await apiFindDictItem({
        type: props.dictType as string,
        value: curLastLevVal as string
      })
      const { isSuccess, errorMsg, data } = res
      if (isSuccess) {
        if (data) {
          curLastLevVal = data.parentValue
          selectedItems.push(data)
          findTimes++

          // 当前层级父id以及父级值等于传入的父级id、值时，停止追溯，表示已经到最上级了
          if (data.parentValue === props.parentValue || data.parentId === props.parentId) {
            findTimes = 100
          }
        } else {
          findTimes = 100
        }
      } else {
        findTimes = 100
        window.$Message.error(errorMsg)
      }
    }

    selectedItems.reverse() // 倒序
    currtentValue.value = selectedItems.map((item: IdictItem) => item.value)

    // change-on-select值默认为false, 回填时不会显示已选中的文本，故手动选择
    setTimeout(() => {
      if (cascaderRef.value) {
        cascaderRef.value.selected = selectedItems
      }
    }, 200)
  } else {
    currtentValue.value = []
  }
}

// dom挂载前方法执行
onBeforeMount(() => {
  init()
})

// 动态获取数据
const loadDataHandle = (item: IdictItem, callback: Function) => {
  item.loading = true
  apiFindDictList({ type: props.dictType, parentId: item.id }).then((res) => {
    const { isSuccess, errorMsg, data } = res || {}
    if (isSuccess) {
      item.children = data?.map((_item: IdictItem) => {
        _item.lev = item.lev + 1

        if (_item.lev === 2) {
          // 二级项禁用判断
          if (props.showableSubVals.length) {
            console.log(props.showableSubVals, _item, 2333)

            const itemShowJudge = props.showableSubVals.includes(_item.value)
            // showableSetReverse为true时，符合条件的选项隐藏；为false时，符合条件的展示
            console.log(itemShowJudge)

            const isItemShow = props.showableSubValsReverse ? itemShowJudge : !itemShowJudge
            _item.disabled = isItemShow
          } else {
            _item.disabled = false
          }
        }

        if (props.showLev !== 999 && props.showLev <= _item.lev) {
          // 根据配置，限制暂时层级
          return { ..._item, childrenSum: 0 }
        } else {
          return (_item.childrenSum as number) > 0 ? { ..._item, children: [], loading: false } : _item
        }
      })
      item.loading = false
    } else {
      window.$Message.error(errorMsg)
    }

    callback()
  })
}

// 选择完成后的回调
const onChangeHandle = (values: string[], selectedData: IdictItem[]) => {
  currtentValue.value = values
  emits('update:modelValue', values[values.length - 1])

  const labels = selectedData.map((item) => item.label)
  emits('get-full-label', labels.join(props.splitChar))
  emits('get-last-label', labels[labels.length - 1])
  emits('on-change', values, labels)
}

// 展开和关闭弹窗时触发
const changeOnSelect = ref(false) // 为 true 时，点选每级菜单选项值都会发生变化
const numberToTxt = ['', '一', '二', '三', '四', '五']
const onVisibleChangeHandle = (value: boolean) => {
  // 根据选择面板的显隐，切换change-on-select的值
  // change-on-select为true时，已选中的值回填会被覆盖，故做动态切换
  changeOnSelect.value = value

  if (!value) {
    levSelectJudgeHandle()
  }
}

// 层级选择判断
const levSelectJudgeHandle = (): boolean => {
  const selectedItems: IdictItem[] = cascaderRef.value.selected
  const lastSelectedItem: IdictItem = selectedItems[selectedItems.length - 1]

  // 数据值第一级回显时，可能出现第一层级值 不存在的情况，手动补充
  if (lastSelectedItem && !lastSelectedItem.lev) {
    lastSelectedItem.lev = 1
  }

  // 最后一级选择判断
  if (props.selLastLev) {
    const isLastLev = selectedItems.length > 0 ? lastSelectedItem.childrenSum === 0 : false
    if (!isLastLev) {
      window.$Message.warning('请选择到最后一级')
      resetValue()
      return false
    }
  }

  // 最小层级选择判断
  if (props.selMinLev !== 0 && props.selMinLev <= props.showLev) {
    // 未选择或者小于配置的层级
    if (!lastSelectedItem || lastSelectedItem.lev < props.selMinLev) {
      window.$Message.warning(`请至少选择到第${numberToTxt[props.selMinLev]}级`)
      resetValue()
      return false
    }
  }

  return true
}

// 重置
const resetValue = () => {
  currtentValue.value = []
  emits('update:modelValue', '')
}

// 向外暴露方法
defineExpose({
  levSelectJudgeHandle,
  resetValue
})

watch(
  () => props.modelValue,
  (newV) => {
    if (!newV) {
      resetValue()
    }
  }
)

// 禁用配置监听 配置项切换时，需重新初始数据
watch([() => props.showableRootVals, () => props.showableSubVals], () => {
  getFirstLevDict()
})

// 字典父级值、id监听 配置项切换时，需重新初始数据
watch([() => props.parentValue, () => props.parentId], () => {
  getFirstLevDict()
})
</script>
